package org.nanhill.support.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.nanhill.commons.pattern.domain.EntityManager;

/**
 * @link http://www.hibernate.org/328.html
 * 
 * @author alin [xalinx at gmail dot com]
 * 
 */
public abstract class HibernateEntityManager<E, ID extends Serializable>
		extends HibernateSupport implements EntityManager<E, ID> {

	private Class<E> entityClass;

	private String entityClassName;

	private String countEntityQuery;

	public HibernateEntityManager() {
		entityClass = (Class<E>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
		entityClassName = entityClass.getCanonicalName();
		countEntityQuery = "select count(*) from " + entityClassName;
	}

	public HibernateEntityManager(SessionFactory sessionFactory) {
		this();
		setSessionFactory(sessionFactory);
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 */
	@SuppressWarnings("unchecked")
	protected List<E> findList(final Criterion... criterion) {
		Criteria crit = getSession().createCriteria(entityClass);
		for (Criterion c : criterion) {
			crit.add(c);
		}
		return crit.list();
	}

	@SuppressWarnings("unchecked")
	protected List<E> findList(final String query, final Object... values) {
		Query q = getSession().createQuery(query);
		HibernateKit.initQuery(q, values);
		return (List<E>) q.list();
	}

	@SuppressWarnings("unchecked")
	protected List<E> findList(final String query) {
		Query q = getSession().createQuery(query);
		return (List<E>) q.list();
	}

	@SuppressWarnings("unchecked")
	protected E findOne(final String query, final Object... values) {
		Query q = getSession().createQuery(query);
		HibernateKit.initQuery(q, values);
		return (E) q.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	protected List<E> findPart(final String query, final int start,
			final int size, final Object... values) {
		Query q = getSession().createQuery(query);
		HibernateKit.initQuery(q, values);
		q.setFirstResult(start);
		q.setMaxResults(size);
		return q.list();
	}

	protected int update(final String query) {
		Query q = getSession().createQuery(query);
		return q.executeUpdate();
	}

	protected int update(final String query, final Object... values) {
		Query q = getSession().createQuery(query);
		HibernateKit.initQuery(q, values);
		return q.executeUpdate();
	}

	@SuppressWarnings("unchecked")
	protected E findOneByNamedQuery(final String queryName,
			final Object... values) {
		Query q = getSession().getNamedQuery(queryName);
		HibernateKit.initQuery(q, values);
		return (E) q.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	protected List<E> findPartByNamedQuery(final String queryName,
			final int start, final int size, final Object... values) {
		Query q = getSession().getNamedQuery(queryName);
		HibernateKit.initQuery(q, values);
		q.setFirstResult(start);
		q.setMaxResults(size);
		return q.list();
	}

	@SuppressWarnings("unchecked")
	protected List<E> findByNamedQuery(Class<E> clz, final String queryName,
			final Object... values) {
		Query q = getSession().getNamedQuery(queryName);
		HibernateKit.initQuery(q, values);
		return q.list();
	}

	public int findCount() {
		return ((Long) getSession().createQuery(countEntityQuery)
				.uniqueResult()).intValue();
	}

	@SuppressWarnings("unchecked")
	public List<E> findAll() {
		Criteria c = getSession().createCriteria(entityClass);
		return c.list();
	}

	@SuppressWarnings("unchecked")
	public List<E> findPart(final int start, final int size) {
		Criteria c = getSession().createCriteria(entityClass);
		c.setFirstResult(start);
		c.setMaxResults(size);
		return c.list();
	}

	/**
	 * Return the persistent instance of the given entity class with the given
	 * identifier, assuming that the instance exists.
	 * 
	 * You should not use this method to determine if an instance exists. Use
	 * this only to retrieve an instance that you assume exists, where
	 * non-existence would be an actual error.
	 * 
	 * @see org.nanhill.commons.pattern.domain.EntityManager#findById(java.io.Serializable)
	 */
	@SuppressWarnings("unchecked")
	public E findById(ID id) {
		// N: Not Session.get(Class theClass, Serializable id)
		return (E) getSession().load(entityClass, id);
	}

	public void save(E entity) {
		getSession().saveOrUpdate(entity);
	}

	public void delete(E entity) {
		getSession().delete(entity);
	}

	public void merge(E entity) {
		getSession().merge(entity);
	}

}
